--- /dev/null
+/* pdb.c
+ *
+ * Functions for dealing with Palm databases and such.
+ *
+ * Copyright (C) 1999-2001, Andrew Arensburger.
+ * You may distribute this file under the terms of the Artistic
+ * License, as specified in the README file.
+ *
+ * $Id: pdb.c,v 1.1 2002-08-16 06:13:10 robertl Exp $
+ */
+/* XXX - The way zero-length records are handled is a bit of a kludge. They
+ * shouldn't normally exist, with the exception of expunged records. But,
+ * of course, a malformed conduit or something can create them.
+ * The half-assed way they're handled here is to a) not upload zero-length
+ * records to the Palm, b) warn the user if they're written to a file, c)
+ * provide a utility (in the p5-Palm package) to delete zero-length
+ * records.
+ */
+/* XXX - This is a library. It shouldn't print error messages.
+ * Add 'int pdb_errno'; define error numbers and error messages that go
+ * with them.
+ * Debugging messages should go to 'FILE *pdb_logfile'.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <fcntl.h> /* For open() */
+#include <sys/types.h>
+// #include <sys/uio.h>
+// #include <sys/param.h> /* For MAXPATHLEN */
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+#if STDC_HEADERS
+# include <string.h> /* For strncat(), memcpy() et al. */
+#else /* STDC_HEADERS */
+# ifndef HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif /* HAVE_STRCHR */
+# ifndef HAVE_MEMCPY
+# define memcpy(d,s,n) bcopy ((s), (d), (n))
+# define memmove(d,s,n) bcopy ((s), (d), (n))
+# endif /* HAVE_MEMCPY */
+#endif /* STDC_HEADERS */
+
+/* XXX - Is this right? Should this be in the "else" clause, above? */
+#if HAVE_STRINGS_H
+# include <strings.h> /* For bzero() */
+#endif /* HAVE_STRINGS_H */
+
+#if HAVE_LIBINTL_H
+# include <libintl.h> /* For i18n */
+#endif /* HAVE_LIBINTL_H */
+
+#include <palm.h>
+#include "pdb.h"
+
+/* XXX - The functions declared INLINE, below, really ought to be inline
+ * functions. I'm not sure how to do this portably, though.
+ */
+#ifdef __GNUC__
+# define INLINE __inline__
+#else
+# define INLINE
+#endif /* __GNUC__ */
+
+/* Functions for extracting values from an array of ubytes */
+extern INLINE ubyte get_ubyte(const ubyte **buf);
+extern INLINE uword get_uword(const ubyte **buf);
+extern INLINE udword get_udword(const ubyte **buf);
+
+/* Functions for writing values to an array of ubytes */
+extern INLINE void put_ubyte(ubyte **buf, const ubyte value);
+extern INLINE void put_uword(ubyte **buf, const uword value);
+extern INLINE void put_udword(ubyte **buf, const udword value);
+extern void debug_dump(FILE *outfile, const char *prefix,
+ const ubyte *buf, const udword len);
+
+int pdb_trace = 0; /* Debugging level for PDB stuff */
+#define PDB_TRACE(n) if (pdb_trace >= (n))
+
+/* Helper functions */
+static uword get_file_length(int fd);
+int pdb_LoadHeader(int fd, struct pdb *db);
+ /* pdb_LoadHeader() is visible to other files */
+static int pdb_LoadRecListHeader(int fd, struct pdb *db);
+static int pdb_LoadRsrcIndex(int fd, struct pdb *db);
+static int pdb_LoadRecIndex(int fd, struct pdb *db);
+static int pdb_LoadAppBlock(int fd, struct pdb *db);
+static int pdb_LoadSortBlock(int fd, struct pdb *db);
+static int pdb_LoadResources(int fd, struct pdb *db);
+static int pdb_LoadRecords(int fd, struct pdb *db);
+
+/* merge_attributes
+ * Takes a record's flags and category, and merges them into a single byte,
+ * with the flags in the top nybble and the category in the bottom one
+ */
+static inline ubyte
+merge_attributes(const ubyte flags,
+ const ubyte category)
+{
+ /* The PDB_REC_ARCHIVED flag is troublesome, since it overlaps the
+ * category field. The idea here is that if the record was deleted,
+ * then it doesn't have a category anymore, so the category part
+ * gets set to 0.
+ */
+ if ((flags & PDB_REC_DELETED) == 0)
+ return (flags & 0xf0) |
+ (category & 0x0f);
+ else
+ return (flags & 0xf8);
+}
+
+/* split_attributes
+ * The converse of merge_attributes(). Takes the combined field attributes
+ * and writes its contents to *flags and *category, using the same rules as
+ * merge_attributes(), above.
+ */
+static inline void
+split_attributes(const ubyte attributes,
+ ubyte *flags,
+ ubyte *category)
+{
+ if ((attributes & PDB_REC_DELETED) == 0)
+ {
+ *flags = (attributes & 0xf0);
+ *category = (attributes & 0x0f);
+ } else {
+ *flags = (attributes & 0xf8);
+ *category = 0;
+ }
+ PDB_TRACE(6)
+ fprintf(stderr, "split 0x%02x into 0x%02x, 0x%02x\n",
+ attributes, *flags, *category);
+}
+
+/* new_pdb
+ * struct pdb constructor.
+ */
+struct pdb *
+new_pdb()
+{
+ struct pdb *retval;
+
+ /* Allocate the new pdb */
+ if ((retval = (struct pdb *) malloc(sizeof(struct pdb))) == NULL)
+ /* Out of memory */
+ return NULL;
+
+ /* Write zeros all over it, just for safety */
+ memset((void *) retval, 0, sizeof(struct pdb));
+
+ return retval;
+}
+
+/* pdb_FreeRecord
+ * Free a previously-allocated 'pdb_record'. This function wouldn't really
+ * be necessary, except that pdb_CopyRecord() returns a 'pdb_record'.
+ */
+void
+pdb_FreeRecord(struct pdb_record *rec)
+{
+ if (rec->data != NULL)
+ free(rec->data);
+ free(rec);
+}
+
+/* pdb_FreeResource
+ * Free a previously-allocated 'pdb_resource'. This function wouldn't
+ * really be necessary, except that pdb_CopyResource() returns a
+ * 'pdb_resource'.
+ */
+void
+pdb_FreeResource(struct pdb_resource *rsrc)
+{
+ if (rsrc->data != NULL)
+ free(rsrc->data);
+ free(rsrc);
+}
+
+/* free_pdb
+ * Cleanly free a struct pdb, and all of its subparts (destructor).
+ */
+void
+free_pdb(struct pdb *db)
+{
+ PDB_TRACE(7)
+ fprintf(stderr, "Inside free_pdb(%p)\n", (void *) db);
+
+ if (db == NULL)
+ /* Trivial case */
+ return;
+
+ /* Free the array of records/resources */
+ if (IS_RSRC_DB(db))
+ {
+ /* It's a resource database */
+ struct pdb_resource *rsrc;
+ struct pdb_resource *next;
+
+ PDB_TRACE(8)
+ fprintf(stderr, "Freeing resource list\n");
+
+ /* Walk the linked list, freeing as we go along */
+ for (rsrc = db->rec_index.rsrc;
+ rsrc != NULL;
+ rsrc = next)
+ {
+ next = rsrc->next; /* Remember the next
+ * element on the list. We
+ * won't have a chance to
+ * look it up after this
+ * one has been free()d.
+ */
+
+ /* Free this element */
+ pdb_FreeResource(rsrc);
+ }
+ } else {
+ /* It's a record database */
+ struct pdb_record *rec;
+ struct pdb_record *next;
+
+ PDB_TRACE(8)
+ fprintf(stderr, "Freeing record list\n");
+
+ /* Walk the linked list, freeing as we go along */
+ for (rec = db->rec_index.rec;
+ rec != NULL;
+ rec = next)
+ {
+ next = rec->next; /* Remember the next
+ * element on the list. We
+ * won't have a chance to
+ * look it up after this
+ * one has been free()d.
+ */
+
+ /* Free this element */
+ pdb_FreeRecord(rec);
+ }
+ }
+
+ /* Free the sort block */
+ if (db->sortinfo != NULL)
+ free(db->sortinfo);
+
+ /* Free the app info block */
+ if (db->appinfo != NULL)
+ free(db->appinfo);
+
+ free(db);
+}
+
+/* pdb_Read
+ * Read a PDB from the file descriptor 'fd'. This must already have been
+ * opened for reading and/or writing.
+ *
+ * Note: this function does not to any locking. The caller is responsible
+ * for that.
+ */
+struct pdb *
+pdb_Read(int fd)
+{
+ int err;
+ struct pdb *retval;
+
+ /* Create a new pdb to return */
+ if ((retval = new_pdb()) == NULL)
+ {
+ return NULL;
+ }
+
+ /* Find out how long the file is */
+ retval->file_size = get_file_length(fd);
+ if (retval->file_size == ~0)
+ {
+ /* The file isn't seekable */
+ fprintf(stderr, _("File isn't seekable.\n"));
+ free_pdb(retval);
+ return NULL;
+ }
+
+ /* Load the header */
+ if ((err = pdb_LoadHeader(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't load header.\n"));
+ free_pdb(retval);
+ return NULL;
+ }
+
+ /* Load the record list header */
+ if ((err = pdb_LoadRecListHeader(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't load record list header for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+
+ /* Read the record/resource list */
+ if (IS_RSRC_DB(retval))
+ {
+ /* Read the resource index */
+ if ((err = pdb_LoadRsrcIndex(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read resource index for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+ } else {
+ /* Read the record index */
+ if ((err = pdb_LoadRecIndex(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read record index for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+ }
+
+ /* In most PDBs, there are two NUL bytes here. They are allowed by
+ * the spec, but not mandated, and some PDBs don't have them. We'll
+ * ignore them for now, and have the appropriate pdb_Load*()
+ * function lseek() to the proper position.
+ */
+
+ /* Load the AppInfo block, if any */
+ if ((err = pdb_LoadAppBlock(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read AppInfo block for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+
+ /* Load the sort block, if any */
+ if ((err = pdb_LoadSortBlock(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read sort block for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+
+ /* Load the records themselves */
+ if (IS_RSRC_DB(retval))
+ {
+ /* Read the resources */
+ if ((err = pdb_LoadResources(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read resources for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+ } else {
+ /* Read the records */
+ if ((err = pdb_LoadRecords(fd, retval)) < 0)
+ {
+ fprintf(stderr, _("Can't read records for "
+ "\"%.*s\".\n"),
+ PDB_DBNAMELEN, retval->name);
+ free_pdb(retval);
+ return NULL;
+ }
+ }
+
+ return retval; /* Success */
+}
+
+/* pdb_Write
+ * Write 'db' to the file descriptor 'fd'. This must already have been
+ * opened for writing.
+ *
+ * Note that while you can open the backup file for reading and writing,
+ * read from it with pdb_Read() and save it with pdb_Write(), this is not
+ * recommended: if anything should go wrong at the wrong time (e.g., the
+ * disk fills up just as you're about to write the database back to disk),
+ * you will lose the entire backup.
+ * A better approach is to use a staging file: read from the backup file,
+ * write to a temporary file, then use rename() to move the temporary file
+ * onto the real one. Alternately, you can copy the original file to a
+ * temporary one, then open the temporary for both reading and writing.
+ * This might have some advantages, in that it allows you to lock a single
+ * file for the duration of the sync.
+ *
+ * Note: this function does not lock the file. The caller is responsible
+ * for that.
+ */
+int
+pdb_Write(const struct pdb *db,
+ int fd)
+{
+ static ubyte header_buf[PDB_HEADER_LEN];
+ /* Buffer for writing database header */
+ static ubyte rlheader_buf[PDB_RECORDLIST_LEN];
+ /* Buffer for writing the record list header */
+ static ubyte nul_buf[2];
+ /* Buffer for writing the two useless NULs */
+ ubyte *wptr; /* Pointer into buffers, for writing */
+ udword offset; /* The next offset we're interested in */
+
+ /* Initialize 'offset': the next variable-sized item will go after
+ * the header, after the index header, after the index, after the
+ * two useless NULs.
+ */
+ offset = PDB_HEADER_LEN + PDB_RECORDLIST_LEN;
+ if (IS_RSRC_DB(db))
+ offset += db->numrecs * PDB_RESOURCEIX_LEN;
+ else
+ offset += db->numrecs * PDB_RECORDIX_LEN;
+ offset += 2; /* Those two useless NUL bytes */
+
+ /** Write the database header **/
+
+ /* Construct the header in 'header_buf' */
+ wptr = header_buf;
+ memcpy(wptr, db->name, PDB_DBNAMELEN);
+ wptr += PDB_DBNAMELEN;
+ put_uword(&wptr, (db->attributes & ~PDB_ATTR_OPEN));
+ /* Clear the 'open' flag before writing */
+ put_uword(&wptr, db->version);
+ put_udword(&wptr, db->ctime);
+ put_udword(&wptr, db->mtime);
+ put_udword(&wptr, db->baktime);
+ put_udword(&wptr, db->modnum);
+ if (db->appinfo == NULL) /* Write the AppInfo block, if any */
+ /* This database doesn't have an AppInfo block */
+ put_udword(&wptr, 0L);
+ else {
+ /* This database has an AppInfo block */
+ put_udword(&wptr, offset);
+ offset += db->appinfo_len;
+ }
+ if (db->sortinfo == NULL) /* Write the sort block, if any */
+ /* This database doesn't have a sort block */
+ put_udword(&wptr, 0L);
+ else {
+ put_udword(&wptr, offset);
+ offset += db->sortinfo_len;
+ }
+ put_udword(&wptr, db->type);
+ put_udword(&wptr, db->creator);
+ put_udword(&wptr, db->uniqueIDseed);
+
+ /* Write the database header */
+ if (write(fd, header_buf, PDB_HEADER_LEN) != PDB_HEADER_LEN)
+ {
+ fprintf(stderr, _("%s: can't write database header for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ close(fd);
+ return -1;
+ }
+
+ /** Write the record/resource index header **/
+ /* Construct the record list header */
+ wptr = rlheader_buf;
+ put_udword(&wptr, 0L); /* nextID */
+ /* XXX - What is this? Should this be something
+ * other than 0? */
+ put_uword(&wptr, db->numrecs);
+
+ /* Write the record list header */
+ if (write(fd, rlheader_buf, PDB_RECORDLIST_LEN) != PDB_RECORDLIST_LEN)
+ {
+ fprintf(stderr, _("%s: can't write record list header for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+
+ /* Write the record/resource index */
+ if (IS_RSRC_DB(db))
+ {
+ /* It's a resource database */
+ struct pdb_resource *rsrc; /* Current resource */
+
+ /* Go through the list of resources, writing each one */
+ for (rsrc = db->rec_index.rsrc;
+ rsrc != NULL;
+ rsrc = rsrc->next)
+ {
+ static ubyte rsrcbuf[PDB_RESOURCEIX_LEN];
+ /* Buffer to hold the resource
+ * index entry.
+ */
+
+ /* Construct the resource index entry */
+ wptr = rsrcbuf;
+ put_udword(&wptr, rsrc->type);
+ put_uword(&wptr, rsrc->id);
+ put_udword(&wptr, offset);
+
+ /* Write the resource index entry */
+ if (write(fd, rsrcbuf, PDB_RESOURCEIX_LEN) !=
+ PDB_RESOURCEIX_LEN)
+ {
+ fprintf(stderr, _("%s: Can't write resource "
+ "index entry for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+
+ /* Bump 'offset' up to point to the offset of the
+ * next variable-sized thing in the file.
+ */
+ offset += rsrc->data_len;
+ }
+ } else {
+ /* It's a record database */
+ struct pdb_record *rec; /* Current record */
+
+ /* Go through the list of records, writing each one */
+ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+ {
+ static ubyte recbuf[PDB_RECORDIX_LEN];
+ /* Buffer to hold the record index
+ * entry.
+ */
+
+ /* Construct the record index entry */
+ wptr = recbuf;
+
+ /* Sanity check */
+ if (rec->data_len == 0)
+ {
+ fprintf(stderr,
+ _("\"%.*s\" record 0x%08lx has "
+ "length 0.\n"),
+ PDB_DBNAMELEN, db->name,
+ rec->id);
+ }
+
+ put_udword(&wptr, offset);
+ put_ubyte(&wptr, merge_attributes(
+ rec->flags,
+ rec->category));
+ put_ubyte(&wptr, (char) ((rec->id >> 16) & 0xff));
+ put_ubyte(&wptr, (char) ((rec->id >> 8) & 0xff));
+ put_ubyte(&wptr, (char) (rec->id & 0xff));
+
+ /* Write the resource index entry */
+ if (write(fd, recbuf, PDB_RECORDIX_LEN) !=
+ PDB_RECORDIX_LEN)
+ {
+ fprintf(stderr, _("%s: Can't write record "
+ "index entry for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+
+ /* Bump 'offset' up to point to the offset of the
+ * next variable-sized thing in the file.
+ */
+ offset += rec->data_len;
+ }
+ }
+
+ /* Write the two useless NUL bytes */
+ nul_buf[0] = nul_buf[1] = '\0';
+ if (write(fd, nul_buf, 2) != 2)
+ {
+ fprintf(stderr, _("%s: Can't write the two useless NULs to "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+
+ /* Write the AppInfo block, if any */
+ if (db->appinfo != NULL)
+ {
+ if (write(fd, db->appinfo, db->appinfo_len) !=
+ db->appinfo_len)
+ {
+ fprintf(stderr, _("%s: Can't write AppInfo block for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+ }
+
+ /* Write the sort block, if any */
+ if (db->sortinfo != NULL)
+ {
+ if (write(fd, db->sortinfo, db->sortinfo_len) !=
+ db->sortinfo_len)
+ {
+ fprintf(stderr, _("%s: Can't write sort block for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+ }
+
+ /* Write the record/resource data */
+ if (IS_RSRC_DB(db))
+ {
+ /* It's a resource database */
+ struct pdb_resource *rsrc;
+
+ /* Go through the list of resources, writing each one's
+ * data.
+ */
+ for (rsrc = db->rec_index.rsrc;
+ rsrc != NULL;
+ rsrc = rsrc->next)
+ {
+ /* Write the data */
+ if (write(fd, rsrc->data, rsrc->data_len) !=
+ rsrc->data_len)
+ {
+ fprintf(stderr, _("%s: Can't write resource "
+ "data for \"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+ }
+ } else {
+ /* It's a record database */
+ struct pdb_record *rec;
+
+ /* Go through the list of records, writing each one's data. */
+ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+ {
+ /* Write the data */
+ if (write(fd, rec->data, rec->data_len) !=
+ rec->data_len)
+ {
+ fprintf(stderr,
+ _("%s: Can't write record data for "
+ "\"%.*s\".\n"),
+ "pdb_Write",
+ PDB_DBNAMELEN, db->name);
+ perror("write");
+ return -1;
+ }
+ }
+ }
+
+ return 0; /* Success */
+}
+
+/* pdb_FindRecordByID
+ * Find the record in 'db' whose ID is 'id'. Return a pointer to it. If no
+ * such record exists, or in case of error, returns NULL.
+ */
+struct pdb_record *
+pdb_FindRecordByID(
+ const struct pdb *db,
+ const udword id)
+{
+ struct pdb_record *rec;
+
+ /* Walk the list of records, comparing IDs */
+ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+ {
+ if (rec->id == id)
+ return rec;
+ }
+
+ return NULL; /* Couldn't find it */
+}
+
+/* pdb_FindRecordByIndex
+ * Find the 'index'th record in 'db', and return a pointer to it. If no
+ * such record exists, or in case of error, return NULL.
+ */
+struct pdb_record *
+pdb_FindRecordByIndex(
+ const struct pdb *db, /* Database to look in */
+ const uword index) /* Index of the record to look for */
+{
+ struct pdb_record *rec;
+ int i;
+
+ /* Walk the list, decrementing the count as we go along. If it
+ * reaches 0, we've found the record.
+ */
+ rec = db->rec_index.rec;
+ for (i = index; i > 0; i--)
+ {
+ if (rec == NULL)
+ /* Oops! We've fallen off the end of the list */
+ return NULL;
+ rec = rec->next;
+ }
+
+ return rec; /* Success */
+}
+
+/* pdb_NextRecord
+ * Find the next record after 'rec' in 'db', and return a pointer to it. If
+ * 'rec' is the last record in the list, return NULL.
+ */
+struct pdb_record *
+pdb_NextRecord(const struct pdb *db, /* Database to look in */
+ const struct pdb_record *rec)
+ /* Return 'rec's successor */
+{
+ return rec->next;
+}
+
+/* pdb_DeleteRecordByID
+ * Find the record whose unique ID is 'id' and delete it from 'db'. If the
+ * record isn't found, well, that's okay; we wanted to delete it anyway.
+ * Returns 0 if successful, -1 in case of error.
+ */
+int
+pdb_DeleteRecordByID(
+ struct pdb *db,
+ const udword id)
+{
+ struct pdb_record *rec; /* Record we're looking at */
+ struct pdb_record *last; /* Last record we saw */
+
+ if (IS_RSRC_DB(db))
+ /* This only works with record databases */
+ return -1;
+
+ /* Look through the list of records */
+ last = NULL; /* Haven't seen any records yet */
+ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+ {
+ /* See if the ID matches */
+ if (rec->id == id)
+ {
+ /* Found it */
+
+ /* XXX - Presumably better to use pdb_FreeRecord() */
+ /* Free 'rec's data */
+ if (rec->data != NULL)
+ free(rec->data);
+
+ /* Cut 'rec' out of the list. The first element of
+ * the list is a special case.
+ */
+ if (last == NULL)
+ db->rec_index.rec = rec->next;
+ else
+ last->next = rec->next;
+
+ free(rec); /* Free it */
+ db->numrecs--; /* Decrement record count */
+
+ return 0; /* Success */
+ }
+
+ last = rec; /* Remember what we just saw */
+ }
+
+ /* Couldn't find it. Oh, well. Call it a success anyway. */
+ return 0;
+}
+
+/* pdb_AppendRecord
+ * Append a new record to 'db's record list. 'newrec' is not copied, so it
+ * is important that the caller not free it afterwards.
+ */
+/* XXX - Ought to make sure that the ID is unique */
+int
+pdb_AppendRecord(struct pdb *db,
+ struct pdb_record *newrec)
+{
+ struct pdb_record *rec;
+
+ /* Sanity check */
+ if (IS_RSRC_DB(db))
+ /* This only works with record databases */
+ return -1;
+
+ /* Check to see if the list is empty */
+ if (db->rec_index.rec == NULL)
+ {
+ db->rec_index.rec = newrec;
+ newrec->next = NULL;
+
+ db->numrecs++; /* Bump record counter */
+
+ return 0; /* Success */
+ }
+
+ /* Walk the list to find its end */
+ for (rec = db->rec_index.rec; rec->next != NULL; rec = rec->next)
+ ;
+ rec->next = newrec;
+ newrec->next = NULL;
+
+ db->numrecs++; /* Bump record counter */
+
+ return 0; /* Success */
+}
+
+/* pdb_AppendResource
+ * Append a new resource to 'db's resource list. 'newrsrc' is not copied,
+ * so it is important that the caller not free it afterwards.
+ */
+int
+pdb_AppendResource(struct pdb *db,
+ struct pdb_resource *newrsrc)
+{
+ struct pdb_resource *rsrc;
+
+ /* Sanity check */
+ if (!IS_RSRC_DB(db))
+ /* This only works with resource databases */
+ return -1;
+
+ /* Check to see if the list is empty */
+ if (db->rec_index.rsrc == NULL)
+ {
+ db->rec_index.rsrc = newrsrc;
+ newrsrc->next = NULL;
+
+ db->numrecs++; /* Bump resource counter */
+
+ return 0; /* Success */
+ }
+
+ /* Walk the list to find its end */
+ for (rsrc = db->rec_index.rsrc; rsrc->next != NULL; rsrc = rsrc->next)
+ ;
+ rsrc->next = newrsrc;
+ newrsrc->next = NULL;
+
+ db->numrecs++; /* Bump resource counter */
+
+ return 0; /* Success */
+}
+
+/* pdb_InsertRecord
+ * Insert 'newrec' into 'db', just after 'prev'. If 'prev' is NULL,
+ * 'newrec' is inserted at the beginning of the list.
+ * Returns 0 if successful, -1 otherwise.
+ * 'newrec' is not copied, so it is important that the caller not free it.
+ */
+int
+pdb_InsertRecord(struct pdb *db, /* The database to insert into */
+ struct pdb_record *prev,
+ /* Insert after this record */
+ struct pdb_record *newrec)
+ /* The record to insert */
+{
+ /* If 'prev' is NULL, insert at the beginning of the list */
+ if (prev == NULL)
+ {
+ newrec->next = db->rec_index.rec;
+ db->rec_index.rec = newrec;
+ db->numrecs++; /* Increment record count */
+
+ return 0; /* Success */
+ }
+
+ /* XXX - This function doesn't actually check to make sure that
+ * 'prev' is in 'db'. You could really fuck yourself over with
+ * this.
+ * So make it a documented requirement.
+ */
+ /* The new record goes in the middle of the list. Insert it. */
+ newrec->next = prev->next;
+ prev->next = newrec;
+ db->numrecs++; /* Increment record count */
+
+ return 0; /* Success */
+}
+
+/* pdb_InsertResource
+ * Insert 'newrsrc' into 'db', just after 'prev'. If 'prev' is NULL, 'newrsrc'
+ * is inserted at the beginning of the list.
+ * Returns 0 if successful, -1 otherwise.
+ * 'newrec' is not copied, so it is important that the caller not free it.
+ */
+int
+pdb_InsertResource(struct pdb *db, /* The database to insert into */
+ struct pdb_resource *prev,
+ /* Insert after this resource */
+ struct pdb_resource *newrsrc)
+ /* The resource to insert */
+{
+ /* If 'prev' is NULL, insert at the beginning of the list */
+ if (prev == NULL)
+ {
+ newrsrc->next = db->rec_index.rsrc;
+ db->rec_index.rsrc = newrsrc;
+ db->numrecs++; /* Increment record count */
+
+ return 0; /* Success */
+ }
+
+ /* XXX - This function doesn't actually check to make sure that
+ * 'prev' is in 'db'. You could really fuck yourself over with
+ * this.
+ * So make it a documented requirement.
+ */
+ /* The new resource goes in the middle of the list. Insert it. */
+ newrsrc->next = prev->next;
+ prev->next = newrsrc;
+ db->numrecs++; /* Increment record count */
+
+ return 0; /* Success */
+}
+
+/* new_Record
+ * Create a new record from the given arguments, and return a pointer to
+ * it. Returns NULL in case of error.
+ * The record data is copied, so the caller needs to take care of freeing
+ * 'data'.
+ *
+ * The 'attributes' and 'category' arguments are combined into one field:
+ * if the new record is deleted, then the category is silently dropped.
+ * Otherwise, the category occupies the bottom 4 bits of the
+ * attributes/category field.
+ */
+struct pdb_record *
+new_Record(const ubyte flags,
+ const ubyte category,
+ const udword id,
+ const uword len,
+ const ubyte *data)
+{
+ struct pdb_record *retval;
+
+ PDB_TRACE(6)
+ {
+ fprintf(stderr, "new_Record: Creating new record:\n");
+ fprintf(stderr, "\tflags == 0x%02x\n", flags);
+ fprintf(stderr, "\tcategory == 0x%02x\n", category);
+ fprintf(stderr, "\tid == 0x%08lx\n", id);
+ fprintf(stderr, "\tlen == %d\n", len);
+ debug_dump(stderr, "NEW", data, len);
+ }
+
+ /* Allocate the record to be returned */
+ if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record)))
+ == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "new_Record");
+ return NULL;
+ }
+
+ /* Initialize the new record */
+ retval->next = NULL;
+ retval->offset = 0L;
+ retval->flags = flags;
+ retval->category = category;
+ retval->id = id;
+
+ /* Allocate space to put the record data */
+ if (len == 0)
+ {
+ /* Special case: the record has no data (e.g., this is an
+ * expunged record).
+ */
+ retval->data_len = len;
+ retval->data = NULL;
+ return retval;
+ }
+
+ if ((retval->data = (ubyte *) malloc(len)) == NULL)
+ {
+ /* Couldn't allocate data portion of record */
+ fprintf(stderr, _("%s: can't allocate data.\n"),
+ "new_Record");
+ free(retval);
+ return NULL;
+ }
+
+ /* Copy the data to the new record */
+ retval->data_len = len;
+ memcpy(retval->data, data, len);
+
+ return retval; /* Success */
+}
+
+/* new_Resource
+ * Create a new resource from the given arguments, and return a pointer to
+ * it. Returns NULL in case of error.
+ * The resource data is copied, so the caller needs to take care of freeing
+ * 'data'.
+ */
+struct pdb_resource *
+new_Resource(const udword type,
+ const uword id,
+ const uword len,
+ const ubyte *data)
+{
+ struct pdb_resource *retval;
+
+ PDB_TRACE(6)
+ {
+ fprintf(stderr, "new_Resource: Creating new resource:\n");
+ fprintf(stderr, "\ttype == 0x%08lx (%c%c%c%c)\n",
+ type,
+ (int) ((type >> 24) & 0xff),
+ (int) ((type >> 16) & 0xff),
+ (int) ((type >> 8) & 0xff),
+ (int) (type & 0xff));
+ fprintf(stderr, "\tid == 0x%04x\n", id);
+ fprintf(stderr, "\tlen == %d\n", len);
+ debug_dump(stderr, "NEW", data, len);
+ }
+
+ /* Allocate the resource to be returned */
+ if ((retval = (struct pdb_resource *)
+ malloc(sizeof(struct pdb_resource))) == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "new_Resource");
+ return NULL;
+ }
+
+ /* Initialize the new resource */
+ retval->next = NULL;
+ retval->offset = 0L;
+ retval->type = type;
+ retval->id = id;
+
+ /* Allocate space to put the resource data */
+ if (len == 0)
+ {
+ /* Special case: zero-length resource (dunno if this should
+ * ever happen, but this way we avoid malloc(0).
+ */
+ retval->data_len = len;
+ retval->data = NULL;
+ return retval;
+ }
+
+ if ((retval->data = (ubyte *) malloc(len)) == NULL)
+ {
+ /* Couldn't allocate data portion of resource */
+ fprintf(stderr, _("%s: can't allocate data.\n"),
+ "new_Resource");
+ free(retval);
+ return NULL;
+ }
+
+ /* Copy the data to the new resource */
+ retval->data_len = len;
+ memcpy(retval->data, data, len);
+
+ return retval; /* Success */
+}
+
+/* pdb_CopyRecord
+ * Make a copy of record 'rec' in database 'db' (and its data), and return
+ * it. The new record is allocated by pdb_CopyRecord(), so the caller has
+ * to take care of freeing it.
+ * Returns a pointer to the new copy, or NULL in case of error.
+ */
+struct pdb_record *pdb_CopyRecord(
+ const struct pdb *db,
+ const struct pdb_record *rec)
+{
+ struct pdb_record *retval;
+
+ /* Allocate the record to be returned */
+ if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record)))
+ == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_CopyRecord");
+ return NULL;
+ }
+
+ retval->next = NULL; /* For cleanliness */
+
+ /* Copy the old record to the new copy */
+ retval->offset = rec->offset;
+ retval->flags = rec->flags;
+ retval->category = rec->category;
+ retval->id = rec->id;
+
+ /* Allocate space for the record data itself */
+ if ((retval->data = (ubyte *) malloc(rec->data_len)) == NULL)
+ {
+ fprintf(stderr, _("%s: can't allocate record data for "
+ "\"%.*s\".\n"),
+ "pdb_CopyRecord",
+ PDB_DBNAMELEN, db->name);
+ free(retval);
+ return NULL;
+ }
+
+ /* Copy the record data */
+ retval->data_len = rec->data_len;
+ memcpy(retval->data, rec->data, retval->data_len);
+
+ return retval; /* Success */
+}
+
+/* pdb_CopyResource
+ * Make a copy of resource 'rsrc' in database 'db' (and its data), and
+ * return it. The new record is allocated by pdb_CopyResource(), so the
+ * caller has to take care of freeing it.
+ * Returns a pointer to the new copy, or NULL in case of error.
+ */
+struct pdb_resource *pdb_CopyResource(
+ const struct pdb *db,
+ const struct pdb_resource *rsrc)
+{
+ struct pdb_resource *retval;
+
+ /* Allocate the resource to be returned */
+ if ((retval = (struct pdb_resource *)
+ malloc(sizeof(struct pdb_resource))) == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_CopyResource");
+ return NULL;
+ }
+
+ retval->next = NULL; /* For cleanliness */
+
+ /* Copy the old resource to the new copy */
+ retval->type = rsrc->type;
+ retval->id = rsrc->id;
+ retval->offset = rsrc->offset;
+
+ /* Allocate space for the record data itself */
+ if ((retval->data = (ubyte *) malloc(rsrc->data_len)) == NULL)
+ {
+ fprintf(stderr, _("%s: can't allocate resource data for "
+ "\"%.*s\".\n"),
+ "pdb_CopyResource",
+ PDB_DBNAMELEN, db->name);
+ free(retval);
+ return NULL;
+ }
+
+ /* Copy the resource data */
+ retval->data_len = rsrc->data_len;
+ memcpy(retval->data, rsrc->data, retval->data_len);
+
+ return retval; /* Success */
+}
+
+/*** Helper functions ***/
+
+/* get_file_length
+ * Return the length of a file, in bytes. In case of error, returns ~0.
+ */
+static uword
+get_file_length(int fd)
+{
+ off_t here;
+ off_t eof;
+
+ /* Get the current position within the file */
+ here = lseek(fd, 0L, SEEK_CUR);
+ if (here < 0)
+ /* The file isn't seekable, presumably either because it
+ * isn't open, or because it's a pipe/socket/FIFO/tty.
+ */
+ return ~0;
+
+ /* Go to the end of the file */
+ eof = lseek(fd, 0L, SEEK_END);
+
+ /* And return to where we were before */
+ lseek(fd, here, SEEK_SET);
+
+ return eof - here;
+}
+
+/* pdb_LoadHeader
+ * Read the header of a pdb file, and fill in the appropriate fields in
+ * 'db'.
+ */
+int
+pdb_LoadHeader(int fd,
+ struct pdb *db)
+{
+ int err;
+ static ubyte buf[PDB_HEADER_LEN];
+ /* Buffer to hold the file header */
+ const ubyte *rptr; /* Pointer into buffers, for reading */
+
+ /* Read the header */
+ if ((err = read(fd, buf, PDB_HEADER_LEN)) != PDB_HEADER_LEN)
+ {
+ perror("pdb_LoadHeader: read");
+ return -1;
+ }
+
+ /* Parse the database header */
+ rptr = buf;
+ memcpy(db->name, buf, PDB_DBNAMELEN);
+ rptr += PDB_DBNAMELEN;
+ db->attributes = get_uword(&rptr);
+ db->version = get_uword(&rptr);
+ db->ctime = get_udword(&rptr);
+ db->mtime = get_udword(&rptr);
+ db->baktime = get_udword(&rptr);
+ db->modnum = get_udword(&rptr);
+ db->appinfo_offset = get_udword(&rptr);
+ db->sortinfo_offset = get_udword(&rptr);
+ db->type = get_udword(&rptr);
+ db->creator = get_udword(&rptr);
+ db->uniqueIDseed = get_udword(&rptr);
+
+ PDB_TRACE(5)
+ {
+ time_t t;
+
+ fprintf(stderr, "\tname: \"%s\"\n", db->name);
+ fprintf(stderr, "\tattributes: 0x%04x", db->attributes);
+ if (db->attributes & PDB_ATTR_RESDB)
+ fprintf(stderr, " RESDB");
+ if (db->attributes & PDB_ATTR_RO) fprintf(stderr, " RO");
+ if (db->attributes & PDB_ATTR_APPINFODIRTY)
+ fprintf(stderr, " APPINFODIRTY");
+ if (db->attributes & PDB_ATTR_BACKUP)
+ fprintf(stderr, " BACKUP");
+ if (db->attributes & PDB_ATTR_OKNEWER)
+ fprintf(stderr, " OKNEWER");
+ if (db->attributes & PDB_ATTR_RESET) fprintf(stderr, " RESET");
+ if (db->attributes & PDB_ATTR_NOCOPY)
+ fprintf(stderr, " NOCOPY");
+ if (db->attributes & PDB_ATTR_STREAM)
+ fprintf(stderr, " STREAM");
+ if (db->attributes & PDB_ATTR_OPEN)
+ fprintf(stderr, " OPEN");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "\tversion: %u\n", db->version);
+ t = db->ctime - EPOCH_1904;
+ fprintf(stderr, "\tctime: %lu %s", db->ctime,
+ ctime(&t));
+ t = db->mtime - EPOCH_1904;
+ fprintf(stderr, "\tmtime: %lu %s", db->mtime,
+ ctime(&t));
+ t = db->baktime - EPOCH_1904;
+ fprintf(stderr, "\tbaktime: %lu %s", db->baktime,
+ ctime(&t));
+ fprintf(stderr, "\tmodnum: %ld\n", db->modnum);
+ fprintf(stderr, "\tappinfo_offset: 0x%08lx\n",
+ db->appinfo_offset);
+ fprintf(stderr, "\tsortinfo_offset: 0x%08lx\n",
+ db->sortinfo_offset);
+ fprintf(stderr, "\ttype: '%c%c%c%c' (0x%08lx)\n",
+ (char) (db->type >> 24) & 0xff,
+ (char) (db->type >> 16) & 0xff,
+ (char) (db->type >> 8) & 0xff,
+ (char) db->type & 0xff,
+ db->type);
+ fprintf(stderr, "\tcreator: '%c%c%c%c' (0x%08lx)\n",
+ (char) (db->creator >> 24) & 0xff,
+ (char) (db->creator >> 16) & 0xff,
+ (char) (db->creator >> 8) & 0xff,
+ (char) db->creator & 0xff,
+ db->creator);
+ fprintf(stderr, "\tuniqueIDseed: %ld\n", db->uniqueIDseed);
+ }
+
+ return 0; /* Success */
+}
+
+/* pdb_LoadRecListHeader
+ * Load the record list header from a pdb file, and fill in the appropriate
+ * fields in 'db'.
+ */
+static int
+pdb_LoadRecListHeader(int fd,
+ struct pdb *db)
+{
+ int err;
+ static ubyte buf[PDB_RECORDLIST_LEN];
+ const ubyte *rptr; /* Pointer into buffers, for reading */
+
+ /* Read the record list header */
+ if ((err = read(fd, buf, PDB_RECORDLIST_LEN)) != PDB_RECORDLIST_LEN)
+ {
+ perror("pdb_LoadRecListHeader: read2");
+ return -1;
+ }
+
+ /* Parse the record list */
+ rptr = buf;
+ db->next_reclistID = get_udword(&rptr);
+ db->numrecs = get_uword(&rptr);
+
+ PDB_TRACE(6)
+ {
+ fprintf(stderr, "\tnextID: %ld\n", db->next_reclistID);
+ fprintf(stderr, "\tlen: %u\n", db->numrecs);
+ }
+
+ return 0;
+}
+
+/* pdb_LoadRsrcIndex
+ * Read the resource index from a resource database file, and fill in the
+ * appropriate fields in 'db'.
+ */
+static int
+pdb_LoadRsrcIndex(int fd,
+ struct pdb *db)
+{
+ int i;
+ int err;
+ uword totalrsrcs; /* The real number of resources in the
+ * database.
+ */
+
+ totalrsrcs = db->numrecs; /* Get the number of resources in
+ * the database. It is necessary to
+ * remember this here because
+ * pdb_AppendResource() increments
+ * db->numrecs in the name of
+ * convenience.
+ */
+
+ if (totalrsrcs == 0)
+ {
+ /* There are no resources in this file */
+ db->rec_index.rsrc = NULL;
+ return 0;
+ }
+
+ /* Read the resource index */
+ for (i = 0; i < totalrsrcs; i++)
+ {
+ static ubyte inbuf[PDB_RESOURCEIX_LEN];
+ /* Input buffer */
+ const ubyte *rptr; /* Pointer into buffers, for reading */
+ struct pdb_resource *rsrc;
+ /* New resource entry */
+
+ /* Allocate the resource entry */
+ if ((rsrc = (struct pdb_resource *)
+ malloc(sizeof(struct pdb_resource)))
+ == NULL)
+ return -1;
+ /* Scribble zeros all over it, just in case */
+ memset((void *) rsrc, 0, sizeof(struct pdb_resource));
+
+ /* Read the next resource index entry */
+ if ((err = read(fd, inbuf, PDB_RESOURCEIX_LEN)) !=
+ PDB_RESOURCEIX_LEN)
+ return -1;
+
+ /* Parse it */
+ rptr = inbuf;
+ rsrc->type = get_udword(&rptr);
+ rsrc->id = get_uword(&rptr);
+ rsrc->offset = get_udword(&rptr);
+
+ PDB_TRACE(6)
+ {
+ fprintf(stderr,
+ "\tResource %d: type '%c%c%c%c' (0x%08lx), "
+ "id %u, offset 0x%08lx\n",
+ i,
+ (char) (rsrc->type >> 24) & 0xff,
+ (char) (rsrc->type >> 16) & 0xff,
+ (char) (rsrc->type >> 8) & 0xff,
+ (char) rsrc->type & 0xff,
+ rsrc->type,
+ rsrc->id,
+ rsrc->offset);
+ }
+
+ /* Append the new resource to the list */
+ pdb_AppendResource(db, rsrc); /* XXX - Error-checking */
+ db->numrecs = totalrsrcs; /* Kludge */
+ }
+
+ return 0;
+}
+
+/* pdb_LoadRecIndex
+ * Read the record index from a record database file, and fill in the
+ * appropriate fields in 'db'.
+ */
+static int
+pdb_LoadRecIndex(int fd,
+ struct pdb *db)
+{
+ int i;
+ int err;
+ uword totalrecs; /* The real number of records in the
+ * database.
+ */
+
+ totalrecs = db->numrecs; /* Get the number of records in the
+ * database. It is necessary to
+ * remember this here because
+ * pdb_AppendResource() increments
+ * db->numrecs in the name of
+ * convenience.
+ */
+
+ if (totalrecs == 0)
+ {
+ /* There are no records in this file */
+ db->rec_index.rec = NULL;
+ return 0;
+ }
+
+ /* Read the record index */
+ /* XXX - It would be a Good Thing to check for zero-length records
+ * here. They've been known to appear as a result of a broken
+ * conduit.
+ */
+ for (i = 0; i < totalrecs; i++)
+ {
+ static ubyte inbuf[PDB_RECORDIX_LEN];
+ /* Input buffer */
+ const ubyte *rptr; /* Pointer into buffers, for reading */
+ struct pdb_record *rec;
+ /* New record entry */
+ ubyte attributes; /* Combined flags+category field */
+
+ /* Allocate the record entry */
+ if ((rec = (struct pdb_record *)
+ malloc(sizeof(struct pdb_record)))
+ == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_LoadRecIndex");
+ return -1;
+ }
+
+ /* Scribble zeros all over it, just in case */
+ memset((void *) rec, 0, sizeof(struct pdb_record));
+
+ /* Read the next record index entry */
+ if ((err = read(fd, inbuf, PDB_RECORDIX_LEN)) !=
+ PDB_RECORDIX_LEN)
+ {
+ fprintf(stderr, _("%s: error reading record index "
+ "entry for \"%.*s\" (%d bytes): "
+ "%d.\n"),
+ "LoadRecIndex",
+ PDB_DBNAMELEN, db->name,
+ PDB_RECORDIX_LEN,
+ err);
+ perror("read");
+ free(rec);
+ return -1;
+ }
+
+ /* Parse it */
+ rptr = inbuf;
+ rec->offset = get_udword(&rptr);
+ attributes = get_ubyte(&rptr);
+ split_attributes(attributes, &(rec->flags), &(rec->category));
+
+ rec->id =
+ ((udword) (get_ubyte(&rptr) << 16)) |
+ ((udword) (get_ubyte(&rptr) << 8)) |
+ ((udword) get_ubyte(&rptr));
+
+ PDB_TRACE(6)
+ fprintf(stderr,
+ "\tRecord %d: offset 0x%08lx, flags 0x%02x, "
+ " category 0x%02x, ID 0x%08lx\n",
+ i,
+ rec->offset,
+ rec->flags,
+ rec->category,
+ rec->id);
+
+ /* Append the new record to the database */
+ pdb_AppendRecord(db, rec); /* XXX - Error-checking */
+ db->numrecs = totalrecs; /* Kludge */
+ }
+
+ return 0;
+}
+
+/* pdb_LoadAppBlock
+ * Read the AppInfo block from a database file, and fill in the appropriate
+ * fields in 'db'. If the file doesn't have an AppInfo block, set it to
+ * NULL.
+ */
+static int
+pdb_LoadAppBlock(int fd,
+ struct pdb *db)
+{
+ int err;
+ localID next_off; /* Offset of the next thing in the file
+ * after the AppInfo block */
+ off_t offset; /* Offset into file, for checking */
+
+ /* Check to see if there even *is* an AppInfo block */
+ if (db->appinfo_offset == 0L)
+ {
+ /* Nope */
+ db->appinfo_len = 0L;
+ db->appinfo = NULL;
+ return 0;
+ }
+
+ /* Figure out how long the AppInfo block is, by comparing its
+ * offset to that of the next thing in the file.
+ */
+ if (db->sortinfo_offset > 0L)
+ /* There's a sort block */
+ next_off = db->sortinfo_offset;
+ else if (db->numrecs > 0)
+ {
+ /* There's no sort block, but there are records. Get the
+ * offset of the first one.
+ */
+ if (IS_RSRC_DB(db))
+ next_off = db->rec_index.rsrc->offset;
+ else
+ next_off = db->rec_index.rec->offset;
+ } else
+ /* There is neither sort block nor records, so the AppInfo
+ * block must go to the end of the file.
+ */
+ next_off = db->file_size;
+
+ /* Subtract the AppInfo block's offset from that of the next thing
+ * in the file to get the AppInfo block's length.
+ */
+ db->appinfo_len = next_off - db->appinfo_offset;
+
+ /* This is probably paranoid, but what the hell */
+ if (db->appinfo_len == 0L)
+ {
+ /* An effective no-op */
+ db->appinfo = NULL;
+ return 0;
+ }
+
+ /* Now that we know the length of the AppInfo block, allocate space
+ * for it and read it.
+ */
+ if ((db->appinfo = (ubyte *) malloc(db->appinfo_len)) == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_LoadAppBlock");
+ return -1;
+ }
+
+ /* Just out of paranoia, make sure we're at the correct offset in
+ * the file. Since the two NULs may or may not have appeared in the
+ * file, the only thing that it makes sense to check is whether
+ * we've already passed the beginning of the AppInfo block, as
+ * given by its offset in the header.
+ */
+ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */
+ if (offset != db->appinfo_offset)
+ {
+ if (offset > db->appinfo_offset)
+ {
+ /* Oops! We're in the wrong place */
+ fprintf(stderr, _("Warning: AppInfo block in \"%.*s\" "
+ "isn't where I thought it would "
+ "be.\n"
+ "Expected 0x%lx, but we're at "
+ "0x%lx.\n"),
+ PDB_DBNAMELEN, db->name,
+ db->appinfo_offset, (long) offset);
+ }
+
+ /* Try to recover */
+ offset = lseek(fd, db->appinfo_offset, SEEK_SET);
+ /* Go to where the AppInfo block
+ * ought to be */
+ if (offset < 0)
+ {
+ /* Something's wrong */
+ fprintf(stderr, _("Can't find the AppInfo block in "
+ "\"%.*s\"!\n"),
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+ }
+
+ /* Read the AppInfo block */
+ if ((err = read(fd, db->appinfo, db->appinfo_len)) != db->appinfo_len)
+ {
+ perror("pdb_LoadAppBlock: read");
+ return -1;
+ }
+ PDB_TRACE(6)
+ debug_dump(stderr, "<APP", db->appinfo, db->appinfo_len);
+
+ return 0;
+}
+
+/* pdb_LoadSortBlock
+ * Read the sort block from a database file, and fill in the appropriate
+ * fields in 'db'. If the file doesn't have a sort block, set it to NULL.
+ *
+ * XXX - Largely untested, since not that many databases have sort blocks.
+ * But it's basically just a clone of pdb_LoadAppBlock(), so it should be
+ * okay.
+ */
+static int
+pdb_LoadSortBlock(int fd,
+ struct pdb *db)
+{
+ int err;
+ localID next_off; /* Offset of the next thing in the file
+ * after the sort block */
+ off_t offset; /* Offset into file, for checking */
+
+ /* Check to see if there even *is* a sort block */
+ if (db->sortinfo_offset == 0L)
+ {
+ /* Nope */
+ db->sortinfo_len = 0L;
+ db->sortinfo = NULL;
+ return 0;
+ }
+
+ /* Figure out how long the sort block is, by comparing its
+ * offset to that of the next thing in the file.
+ */
+ if (db->numrecs > 0)
+ {
+ /* There are records. Get the offset of the first one.
+ */
+ if (IS_RSRC_DB(db))
+ next_off = db->rec_index.rsrc->offset;
+ else
+ next_off = db->rec_index.rec->offset;
+ } else
+ /* There are no records, so the sort block must go to the
+ * end of the file.
+ */
+ next_off = db->file_size;
+
+ /* Subtract the sort block's offset from that of the next thing
+ * in the file to get the sort block's length.
+ */
+ db->sortinfo_len = next_off - db->sortinfo_offset;
+
+ /* This is probably paranoid, but what the hell */
+ if (db->sortinfo_len == 0L)
+ {
+ /* An effective no-op */
+ db->sortinfo = NULL;
+ return 0;
+ }
+
+ /* Now that we know the length of the sort block, allocate space
+ * for it and read it.
+ */
+ if ((db->sortinfo = (ubyte *) malloc(db->sortinfo_len)) == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_LoadSortBlock");
+ return -1;
+ }
+
+ /* Just out of paranoia, make sure we're at the correct offset in
+ * the file. Since the two NULs may or may not have appeared in the
+ * file, the only thing that it makes sense to check is whether
+ * we've already passed the beginning of the sort block, as given
+ * by its offset in the header.
+ */
+ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */
+ if (offset != db->sortinfo_offset)
+ {
+ if (offset > db->sortinfo_offset)
+ {
+ /* Oops! We're in the wrong place */
+ fprintf(stderr, _("Warning: sort block in \"%.*s\" "
+ "isn't where I thought it would "
+ "be.\n"
+ "Expected 0x%lx, but we're at "
+ "0x%lx.\n"),
+ PDB_DBNAMELEN, db->name,
+ db->sortinfo_offset, (long) offset);
+ }
+
+ /* Try to recover */
+ offset = lseek(fd, db->sortinfo_offset, SEEK_SET);
+ /* Go to where the sort block
+ * ought to be */
+ if (offset < 0)
+ {
+ /* Something's wrong */
+ fprintf(stderr, _("Can't find the sort block in "
+ "\"%.*s\"!\n"),
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+ }
+
+ /* Read the sort block */
+ if ((err = read(fd, db->sortinfo, db->sortinfo_len)) !=
+ db->sortinfo_len)
+ {
+ perror("pdb_LoadSortBlock: read");
+ return -1;
+ }
+ PDB_TRACE(6)
+ debug_dump(stderr, "<SORT", db->sortinfo, db->sortinfo_len);
+
+ return 0;
+}
+
+/* pdb_LoadResources
+ * Read each resource in turn from a resource database file.
+ */
+static int
+pdb_LoadResources(int fd,
+ struct pdb *db)
+{
+ int i;
+ int err;
+ struct pdb_resource *rsrc;
+
+ /* This assumes that the resource list has already been created by
+ * 'pdb_LoadRsrcIndex()'.
+ */
+ for (i = 0, rsrc = db->rec_index.rsrc;
+ i < db->numrecs;
+ i++, rsrc = rsrc->next)
+ {
+ off_t offset; /* Current offset, for checking */
+ udword next_off; /* Offset of next resource in file */
+
+ /* Sanity check: make sure we haven't stepped off the end
+ * of the list.
+ */
+ if (rsrc == NULL)
+ {
+ fprintf(stderr, _("Hey! I can't find the %dth "
+ "resource in \"%.*s\"!\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+
+ PDB_TRACE(5)
+ fprintf(stderr,
+ "Reading resource %d (type '%c%c%c%c')\n",
+ i,
+ (char) (rsrc->type >> 24) & 0xff,
+ (char) (rsrc->type >> 16) & 0xff,
+ (char) (rsrc->type >> 8) & 0xff,
+ (char) rsrc->type & 0xff);
+
+ /* Out of paranoia, make sure we're in the right place.
+ * Since the two NULs may or may not have appeared in the
+ * file, the only thing that it makes sense to check is
+ * whether we've already passed the beginning of the
+ * resource, as given by its offset in the resource index.
+ */
+ offset = lseek(fd, 0L, SEEK_CUR);
+ /* Find out where we are now */
+ if (offset != rsrc->offset)
+ {
+ if (offset > rsrc->offset)
+ {
+ fprintf(stderr, _("Warning: resource %d in "
+ "\"%.*s\" isn't where "
+ "I thought it would be.\n"
+ "Expected 0x%lx, but we're "
+ "at 0x%lx.\n"),
+ i,
+ PDB_DBNAMELEN, db->name,
+ rsrc->offset, (long) offset);
+ }
+
+ /* Try to recover */
+ offset = lseek(fd, rsrc->offset, SEEK_SET);
+ /* Go to where this
+ * resource ought to be.
+ */
+ if (offset < 0)
+ {
+ /* Something's wrong */
+ fprintf(stderr, _("Can't find resource %d in "
+ "\"%.*s\".\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+ }
+
+ /* Okay, now that we're in the right place, find out what
+ * the next thing in the file is: its offset will tell us
+ * how much to read.
+ * It's debatable whether 'i' or 'rsrc' should be
+ * authoritative for determining the offset of the next
+ * resource. I'm going to choose 'rsrc', since I think
+ * that's more likely to be immune to fencepost errors. The
+ * two should, however, be equivalent. In fact, it might be
+ * a Good Thing to add a check to make sure.
+ */
+ if (rsrc->next == NULL)
+ {
+ /* This is the last resource in the file, so it
+ * goes to the end of the file.
+ */
+ next_off = db->file_size;
+ } else {
+ /* This isn't the last resource. Find the next
+ * one's offset.
+ */
+ next_off = rsrc->next->offset;
+ }
+
+ /* Subtract this resource's index from that of the next
+ * thing, to get the size of this resource.
+ */
+ rsrc->data_len = next_off - rsrc->offset;
+
+ /* Allocate space for this resource */
+ if ((rsrc->data = (ubyte *) malloc(rsrc->data_len)) == NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_LoadResources");
+ return -1;
+ }
+
+ /* Read the resource */
+ if ((err = read(fd, rsrc->data, rsrc->data_len)) !=
+ rsrc->data_len)
+ {
+ fprintf(stderr, _("Can't read resource %d in "
+ "\"%.*s\".\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ perror("pdb_LoadResources: read");
+ return -1;
+ }
+ PDB_TRACE(6)
+ {
+ fprintf(stderr, "Contents of resource %d:\n", i);
+ debug_dump(stderr, "<RSRC", rsrc->data,
+ rsrc->data_len);
+ }
+ }
+
+ return 0; /* Success */
+}
+
+/* pdb_LoadRecords
+ * Read each record in turn from a record database file.
+ */
+static int
+pdb_LoadRecords(int fd,
+ struct pdb *db)
+{
+ int i;
+ int err;
+ struct pdb_record *rec;
+
+ /* This assumes that the record list has already been created by
+ * 'pdb_LoadRecIndex()'.
+ */
+ for (i = 0, rec = db->rec_index.rec;
+ i < db->numrecs;
+ i++, rec = rec->next)
+ {
+ off_t offset; /* Current offset, for checking */
+ localID next_off; /* Offset of next resource in file */
+
+ /* Sanity check: make sure we haven't stepped off the end
+ * of the list.
+ */
+ if (rec == NULL)
+ {
+ fprintf(stderr, _("Hey! I can't find the %dth "
+ "record in \"%.*s\"!\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+
+ PDB_TRACE(5)
+ fprintf(stderr, "Reading record %d (id 0x%08lx)\n",
+ i, rec->id);
+
+ /* Out of paranoia, make sure we're in the right place.
+ * Since the two NULs may or may not have appeared in the
+ * file, the only thing that it makes sense to check is
+ * whether we've already passed the beginning of the
+ * record, as given by its offset in the record index.
+ */
+ offset = lseek(fd, 0L, SEEK_CUR);
+ /* Find out where we are now */
+ if (offset != rec->offset)
+ {
+ if (offset > rec->offset)
+ {
+ fprintf(stderr, _("Warning: record %d in "
+ "\"%.*s\" isn't where "
+ "I thought it would be.\n"
+ "Expected 0x%lx, but we're "
+ "at 0x%lx.\n"),
+ i,
+ PDB_DBNAMELEN, db->name,
+ rec->offset, (long) offset);
+ }
+
+ /* Try to recover */
+ offset = lseek(fd, rec->offset, SEEK_SET);
+ /* Go to where this record
+ * ought to be. */
+ if (offset < 0)
+ {
+ /* Something's wrong */
+ fprintf(stderr, _("Can't find record %d in "
+ "\"%.*s\".\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ return -1;
+ }
+ }
+
+ /* Okay, now that we're in the right place, find out what
+ * the next thing in the file is: its offset will tell us
+ * how much to read.
+ * It's debatable whether 'i' or 'rec' should be
+ * authoritative for determining the offset of the next
+ * resource. I'm going to choose 'rec', since I think
+ * that's more likely to be immune from fencepost errors.
+ * The two should, however, be equivalent. In fact, it
+ * might be a Good Thing to add a check to make sure.
+ */
+ if (rec->next == NULL)
+ {
+ /* This is the last record in the file, so it goes
+ * to the end of the file.
+ */
+ next_off = db->file_size;
+ } else {
+ /* This isn't the last record. Find the next one's
+ * offset.
+ */
+ next_off = rec->next->offset;
+ }
+
+ /* Subtract this record's index from that of the next one,
+ * to get the size of this record.
+ */
+ rec->data_len = next_off - rec->offset;
+
+ /* Allocate space for this record
+ * If there's a record with length zero, don't pass that to
+ * malloc(). This is most likely due to a broken conduit.
+ * XXX - The Right Thing to do would be not to read
+ * zero-length records, but that would involve fixing the
+ * record index.
+ */
+ if (rec->data_len > 0)
+ {
+ if ((rec->data = (ubyte *) malloc(rec->data_len)) ==
+ NULL)
+ {
+ fprintf(stderr, _("%s: Out of memory.\n"),
+ "pdb_LoadRecords");
+ return -1;
+ }
+
+ /* Read the record */
+ if ((err = read(fd, rec->data, rec->data_len)) !=
+ rec->data_len)
+ {
+ fprintf(stderr, _("Can't read record %d in "
+ "\"%.*s\".\n"),
+ i,
+ PDB_DBNAMELEN, db->name);
+ perror("pdb_LoadRecords: read");
+ return -1;
+ }
+
+ PDB_TRACE(6)
+ {
+ fprintf(stderr, "Contents of record %d:\n", i);
+ debug_dump(stderr, "<REC", rec->data,
+ rec->data_len);
+ }
+ }
+ }
+
+ return 0; /* Success */
+}
+
+\f/* This is for Emacs's benefit:
+ * Local Variables: ***
+ * fill-column: 75 ***
+ * End: ***
+ */